using System;
using System.Collections;
using System.Reflection;

using Evaluant.Uss.Collections;
using Evaluant.Uss.ObjectContext;
using Evaluant.Uss.ObjectContext.Descriptors;
using Evaluant.Uss.Models;

namespace Evaluant.Uss.ObjectContext.DynamicProxy
{

	public class ReflectionEntityGetter
	{
		private IPersistentDescriptor _Descriptor;
        private Model _Model;

		/// <summary>
		/// Get only the specified properties
		/// </summary>
		public ReflectionEntityGetter(IPersistentDescriptor descriptor, Model model)
		{
			_Descriptor = descriptor;
            _Model = model;
		}

        public Entity Get(object target, Hashtable locallyProcessed, Hashtable globallyProcessed)
        {
            if (locallyProcessed.ContainsKey(target))
                return (Entity)locallyProcessed[target];

            Type targetType = target.GetType();

            Entity e = null;

            // If it is a proxy get the underlying entity
            if (target is IPersistableProxy)
            {
                IPersistableProxy proxy = target as IPersistableProxy;

				// Makes an update on the current object so that the entity reflects its private values
				proxy.Update();

				e = proxy.Entity;

                // A cyclic reference can already process the current item
                if (locallyProcessed.ContainsKey(target))
                    locallyProcessed.Add(target, e);

				// Explicitly updates none IPersistable references
				foreach (PropertyDescriptor prop in _Descriptor.GetPersistentProperties(targetType))
				{
					if (!prop.IsEntity)
					{
						continue;
					}

					// Ignores this member (attribute or reference) if not in metadata
					if (_Model.GetAttribute(e.Type, prop.PropertyName) == null
						&& _Model.GetReference(e.Type, prop.PropertyName) == null)
					{
						continue;
					}

                    object fieldValue = prop.GetValue(targetType, target);

					if (fieldValue == null)
						continue;

					if (prop.IsList)
					{
						foreach (object o in (IEnumerable)fieldValue)
						{
							Get(o, locallyProcessed, globallyProcessed);
						}
					}
					else
					{
						Get(fieldValue, locallyProcessed, globallyProcessed);
					}
				}

                return e;
			}
            else if(target is IPersistable)
            {
                e = ((IPersistable)target).Entity;

                globallyProcessed.Add(target, e);
                locallyProcessed.Add(target, e);

                return e;
            }

            // the new object has already been processed ?
            if (globallyProcessed.ContainsKey(target))
            {
                e = (Entity)globallyProcessed[target];
            }
            else
            {
                e = new Entity(ObjectContext.ConvertNamespaceDomainToEuss(targetType.FullName));
                globallyProcessed.Add(target, e);
                locallyProcessed.Add(target, e);
            }
			
			foreach(PropertyDescriptor prop in _Descriptor.GetPersistentProperties(targetType))
			{
                // Ignores this member (attribute or reference) if not in metadata
                if (_Model.GetAttribute(e.Type, prop.PropertyName) == null 
                    && _Model.GetReference(e.Type, prop.PropertyName) == null)
                    continue;

                object fieldValue = prop.GetValue(targetType, target);

				if (fieldValue == null)
					continue;

                if (!prop.IsEntity)
                {
					if (prop.Type.IsEnum)
						if (_Model.GetAttribute(targetType.FullName.Replace('.', ':'), prop.PropertyName).Type == typeof(string))
							e.SetValue(prop.PropertyName, Enum.GetName(prop.Type, fieldValue));
						else
							e.SetValue(prop.PropertyName, (int)fieldValue);
					else
						e.SetValue(prop.PropertyName, fieldValue);
                }
                else
                {
                    if (e.State == State.New)
                    {
                        if (prop.IsList)
                        {
                            foreach (object o in (IEnumerable)fieldValue)
                            {
                                Entity r = Get(o, locallyProcessed, globallyProcessed);
                                e.AddValue(prop.PropertyName, r);
                            }
                        }
                        else
                        {
                            e.SetValue(prop.PropertyName, Get(fieldValue, locallyProcessed, globallyProcessed));
                        }
                    }
                    else
                    {
                        if (prop.IsList)
                        {
                            EntitySet newRels = new EntitySet();

                            foreach (object o in (IEnumerable)fieldValue)
                            {
                                Entity r = Get(o, locallyProcessed, globallyProcessed);
                                newRels.Add(r);
                            }

                            // Removes removed entities
                            foreach (Entity r in e.GetEntitySet(prop.PropertyName))
                            {
                                if (!newRels.Contains(r))
                                    e.RemoveReference(prop.PropertyName, r.Id);
                            }

                            EntitySet oldRels = e.GetEntitySet(prop.PropertyName);

                            // Add new references
                            foreach (Entity r in newRels)
                            {
                                if (!oldRels.Contains(r))
                                    e.AddValue(prop.PropertyName, r);
                            }
                        }
                        else
                        {
                            Entity r = Get(fieldValue, locallyProcessed, globallyProcessed);

                            if (r.State == State.New)
                                e.AddValue(prop.PropertyName, r);
                            else
                            {
                                Entity old = e.GetEntity(prop.PropertyName);

                                if (r.Id != old.Id)
                                {
                                    e.RemoveReference(prop.PropertyName, old.Id);
                                    e.AddValue(prop.PropertyName, r);
                                }
                            }
                        }

                    }
                }
			}

            // Updates the Entity's id to be serialized with the one in the object (for assigned values)
            PropertyDescriptor idPropertyDesc = _Descriptor.GetIdDescriptor(targetType);
            if (idPropertyDesc != null && e.State == State.New)
            {
                object idFieldValue = idPropertyDesc.GetValue(targetType, target);

                if (idPropertyDesc.Type == typeof(string))
                {
                    if (idFieldValue != null)
                    {
                        e.Id = idFieldValue.ToString();
                    }
                    else
                    {
                        idPropertyDesc.SetValue(targetType, target, e.Id);
                    }
                }
                else if (idPropertyDesc.Type == typeof(Int32) && (int)idFieldValue != 0)
                {
                    e.Id = idFieldValue.ToString();
                }
            }

			return e;
		}
	}
}
